"""
文件上传系统主应用

完整的企业级文件上传系统实现
"""

from fastapi import FastAPI, File, UploadFile, Depends, HTTPException, status, Request, Query
from fastapi.responses import FileResponse, StreamingResponse
from sqlalchemy.orm import Session
from typing import List, Optional
import shutil
import math
from pathlib import Path
import sys

# 处理导入路径
if __name__ == "__main__" or "." not in __name__:
    sys.path.insert(0, str(Path(__file__).parent.parent.parent))
    from stage2_advanced.chapter03_file_upload import crud, schemas, models
    from stage2_advanced.chapter03_file_upload.database import get_db, init_db
    from stage2_advanced.chapter03_file_upload.config import (
        ALLOWED_IMAGE_EXTENSIONS,
        ALLOWED_IMAGE_MIMETYPES,
        ALLOWED_DOCUMENT_EXTENSIONS,
        ALLOWED_DOCUMENT_MIMETYPES,
        MAX_IMAGE_SIZE,
        MAX_DOCUMENT_SIZE,
        format_file_size,
        get_upload_path,
    )
    from stage2_advanced.chapter03_file_upload.file_utils import (
        validate_file_size,
        validate_file_extension,
        validate_file_content,
        save_upload_file,
        delete_file,
        get_file_info,
        calculate_file_hash,
    )
    from stage2_advanced.chapter03_file_upload.image_utils import (
        validate_image,
        get_image_info,
        create_thumbnail,
        create_multiple_thumbnails,
        crop_image,
        rotate_image,
        add_text_watermark,
        apply_filter,
    )
else:
    from . import crud, schemas, models
    from .database import get_db, init_db
    from .config import (
        ALLOWED_IMAGE_EXTENSIONS,
        ALLOWED_IMAGE_MIMETYPES,
        ALLOWED_DOCUMENT_EXTENSIONS,
        ALLOWED_DOCUMENT_MIMETYPES,
        MAX_IMAGE_SIZE,
        MAX_DOCUMENT_SIZE,
        format_file_size,
        get_upload_path,
    )
    from .file_utils import (
        validate_file_size,
        validate_file_extension,
        validate_file_content,
        save_upload_file,
        delete_file,
        get_file_info,
        calculate_file_hash,
    )
    from .image_utils import (
        validate_image,
        get_image_info,
        create_thumbnail,
        create_multiple_thumbnails,
        crop_image,
        rotate_image,
        add_text_watermark,
        apply_filter,
    )


# ========== 创建 FastAPI 应用 ==========

app = FastAPI(
    title="文件上传系统",
    description="企业级文件上传系统，支持图片、文档、视频等多种文件类型",
    version="1.0.0",
)


# ========== 启动事件 ==========

@app.on_event("startup")
def on_startup():
    """应用启动时初始化数据库"""
    init_db()
    print("✅ 数据库初始化完成")


# ========== 辅助函数 ==========

def get_file_url(request: Request, filename: str, subdir: str = "") -> str:
    """
    生成文件访问 URL
    
    Args:
        request: 请求对象
        filename: 文件名
        subdir: 子目录
        
    Returns:
        完整的文件 URL
    """
    base_url = str(request.base_url).rstrip('/')
    if subdir:
        return f"{base_url}/files/{subdir}/{filename}"
    return f"{base_url}/files/{filename}"


def add_file_urls(request: Request, file: models.UploadedFile, response: dict) -> dict:
    """
    为文件响应添加 URL
    
    Args:
        request: 请求对象
        file: 文件对象
        response: 响应字典
        
    Returns:
        添加 URL 后的响应字典
    """
    response["url"] = get_file_url(request, file.stored_filename)
    response["download_url"] = f"{str(request.base_url).rstrip('/')}/files/download/{file.id}"
    response["file_size_formatted"] = format_file_size(file.file_size)
    
    # 添加缩略图 URL
    if file.thumbnails:
        for thumbnail in response.get("thumbnails", []):
            thumbnail["url"] = get_file_url(request, thumbnail["filename"], f"thumbnails/{thumbnail['size_name']}")
    
    return response


# ========== 文件上传端点 ==========

@app.post("/upload/image", response_model=schemas.FileUploadResponse, tags=["文件上传"])
async def upload_image(
    request: Request,
    file: UploadFile = File(...),
    description: Optional[str] = None,
    tags: Optional[str] = None,
    is_public: bool = False,
    create_thumbnails: bool = True,
    db: Session = Depends(get_db)
):
    """
    上传图片文件
    
    - **file**: 图片文件
    - **description**: 文件描述（可选）
    - **tags**: 标签，逗号分隔（可选）
    - **is_public**: 是否公开（默认 False）
    - **create_thumbnails**: 是否生成缩略图（默认 True）
    
    支持的格式：JPG, PNG, GIF, WEBP, BMP
    最大文件大小：5 MB
    """
    # 1. 验证文件扩展名
    validate_file_extension(file.filename, ALLOWED_IMAGE_EXTENSIONS)
    
    # 2. 验证文件大小
    validate_file_size(file, MAX_IMAGE_SIZE)
    
    # 3. 验证文件内容
    validate_file_content(file, ALLOWED_IMAGE_MIMETYPES)
    
    # 4. 保存文件
    filename, file_path = await save_upload_file(file, subdir="images")
    
    # 5. 验证是否为有效图片
    if not validate_image(file_path):
        delete_file(file_path)
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Invalid image file"
        )
    
    # 6. 获取图片信息
    image_info = get_image_info(file_path)
    
    # 7. 计算文件哈希
    file_hash = calculate_file_hash(file_path)
    
    # 8. 获取客户端 IP
    client_ip = request.client.host if request.client else None
    
    # 9. 创建数据库记录
    db_file = crud.create_file(
        db=db,
        filename=file.filename,
        stored_filename=filename,
        file_path=str(file_path),
        file_type="image",
        file_size=image_info["size"],
        mime_type=file.content_type,
        file_hash=file_hash,
        width=image_info["width"],
        height=image_info["height"],
        uploader_ip=client_ip,
        description=description,
        tags=tags,
        is_public=is_public
    )
    
    # 10. 生成缩略图
    if create_thumbnails:
        try:
            thumbnails = create_multiple_thumbnails(file_path)
            for size_name, thumbnail_path in thumbnails.items():
                thumb_info = get_image_info(thumbnail_path)
                crud.create_thumbnail(
                    db=db,
                    file_id=db_file.id,
                    size_name=size_name,
                    filename=thumbnail_path.name,
                    file_path=str(thumbnail_path),
                    width=thumb_info["width"],
                    height=thumb_info["height"],
                    file_size=thumb_info["size"]
                )
        except Exception as e:
            print(f"Failed to create thumbnails: {e}")
    
    # 11. 刷新以获取关联数据
    db.refresh(db_file)
    
    # 12. 构建响应
    response = schemas.FileUploadResponse.model_validate(db_file)
    response_dict = response.model_dump()
    response_dict = add_file_urls(request, db_file, response_dict)
    
    return response_dict


@app.post("/upload/document", response_model=schemas.FileUploadResponse, tags=["文件上传"])
async def upload_document(
    request: Request,
    file: UploadFile = File(...),
    description: Optional[str] = None,
    tags: Optional[str] = None,
    is_public: bool = False,
    db: Session = Depends(get_db)
):
    """
    上传文档文件
    
    - **file**: 文档文件
    - **description**: 文件描述（可选）
    - **tags**: 标签，逗号分隔（可选）
    - **is_public**: 是否公开（默认 False）
    
    支持的格式：PDF, DOC, DOCX, TXT, XLSX, XLS, PPT, PPTX
    最大文件大小：10 MB
    """
    # 1. 验证文件
    validate_file_extension(file.filename, ALLOWED_DOCUMENT_EXTENSIONS)
    validate_file_size(file, MAX_DOCUMENT_SIZE)
    validate_file_content(file, ALLOWED_DOCUMENT_MIMETYPES)
    
    # 2. 保存文件
    filename, file_path = await save_upload_file(file, subdir="documents")
    
    # 3. 获取文件信息
    file_info = get_file_info(file_path)
    
    # 4. 计算文件哈希
    file_hash = calculate_file_hash(file_path)
    
    # 5. 创建数据库记录
    client_ip = request.client.host if request.client else None
    
    db_file = crud.create_file(
        db=db,
        filename=file.filename,
        stored_filename=filename,
        file_path=str(file_path),
        file_type="document",
        file_size=file_info["size"],
        mime_type=file.content_type,
        file_hash=file_hash,
        uploader_ip=client_ip,
        description=description,
        tags=tags,
        is_public=is_public
    )
    
    # 6. 构建响应
    response = schemas.FileUploadResponse.model_validate(db_file)
    response_dict = response.model_dump()
    response_dict = add_file_urls(request, db_file, response_dict)
    
    return response_dict


@app.post("/upload/multiple", response_model=List[schemas.FileUploadResponse], tags=["文件上传"])
async def upload_multiple_files(
    request: Request,
    files: List[UploadFile] = File(...),
    is_public: bool = False,
    db: Session = Depends(get_db)
):
    """
    批量上传文件
    
    - **files**: 文件列表（最多 10 个）
    - **is_public**: 是否公开（默认 False）
    
    自动识别文件类型（图片/文档）
    """
    if len(files) > 10:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Maximum 10 files allowed per upload"
        )
    
    uploaded_files = []
    
    for file in files:
        # 判断文件类型
        ext = Path(file.filename).suffix.lower()
        
        if ext in ALLOWED_IMAGE_EXTENSIONS:
            # 上传图片
            try:
                result = await upload_image(request, file, is_public=is_public, db=db)
                uploaded_files.append(result)
            except HTTPException as e:
                print(f"Failed to upload {file.filename}: {e.detail}")
        elif ext in ALLOWED_DOCUMENT_EXTENSIONS:
            # 上传文档
            try:
                result = await upload_document(request, file, is_public=is_public, db=db)
                uploaded_files.append(result)
            except HTTPException as e:
                print(f"Failed to upload {file.filename}: {e.detail}")
        else:
            print(f"Unsupported file type: {file.filename}")
    
    return uploaded_files


# ========== 文件下载和访问端点 ==========

@app.get("/files/{filename}", tags=["文件访问"])
async def get_file(filename: str):
    """
    访问文件（直接显示）
    
    - **filename**: 文件名
    """
    file_path = get_upload_path(filename, subdir="images")
    
    if not file_path.exists():
        file_path = get_upload_path(filename, subdir="documents")
    
    if not file_path.exists():
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="File not found"
        )
    
    return FileResponse(file_path)


@app.get("/files/download/{file_id}", tags=["文件访问"])
async def download_file(file_id: int, db: Session = Depends(get_db)):
    """
    下载文件
    
    - **file_id**: 文件 ID
    """
    db_file = crud.get_file(db, file_id=file_id)
    if not db_file:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="File not found"
        )
    
    file_path = Path(db_file.file_path)
    if not file_path.exists():
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="File not found on disk"
        )
    
    return FileResponse(
        file_path,
        filename=db_file.filename,
        media_type=db_file.mime_type
    )


# ========== 文件管理端点 ==========

@app.get("/files", response_model=schemas.FileListResponse, tags=["文件管理"])
async def list_files(
    request: Request,
    page: int = Query(1, ge=1, description="页码"),
    page_size: int = Query(20, ge=1, le=100, description="每页数量"),
    file_type: Optional[str] = Query(None, description="文件类型过滤"),
    is_public: Optional[bool] = Query(None, description="是否公开过滤"),
    db: Session = Depends(get_db)
):
    """
    获取文件列表（分页）
    
    - **page**: 页码（从 1 开始）
    - **page_size**: 每页数量（最多 100）
    - **file_type**: 文件类型过滤（可选）
    - **is_public**: 是否公开过滤（可选）
    """
    skip = (page - 1) * page_size
    
    # 获取文件列表
    files = crud.get_files(
        db,
        skip=skip,
        limit=page_size,
        file_type=file_type,
        is_public=is_public
    )
    
    # 获取总数
    total = crud.count_files(db, file_type=file_type, is_public=is_public)
    
    # 计算总页数
    total_pages = math.ceil(total / page_size)
    
    # 构建列表项
    items = []
    for file in files:
        item = schemas.FileListItem.model_validate(file)
        item_dict = item.model_dump()
        item_dict["file_size_formatted"] = format_file_size(file.file_size)
        item_dict["url"] = get_file_url(request, file.stored_filename)
        
        # 添加缩略图 URL（仅图片）
        if file.file_type == "image" and file.thumbnails:
            medium_thumb = next((t for t in file.thumbnails if t.size_name == "medium"), None)
            if medium_thumb:
                item_dict["thumbnail_url"] = get_file_url(
                    request,
                    medium_thumb.filename,
                    f"thumbnails/{medium_thumb.size_name}"
                )
        
        items.append(item_dict)
    
    return {
        "total": total,
        "items": items,
        "page": page,
        "page_size": page_size,
        "total_pages": total_pages
    }


@app.get("/files/{file_id}/info", response_model=schemas.FileUploadResponse, tags=["文件管理"])
async def get_file_info_endpoint(
    file_id: int,
    request: Request,
    db: Session = Depends(get_db)
):
    """
    获取文件详细信息
    
    - **file_id**: 文件 ID
    """
    db_file = crud.get_file(db, file_id=file_id)
    if not db_file:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="File not found"
        )
    
    response = schemas.FileUploadResponse.model_validate(db_file)
    response_dict = response.model_dump()
    response_dict = add_file_urls(request, db_file, response_dict)
    
    return response_dict


@app.put("/files/{file_id}", response_model=schemas.FileUploadResponse, tags=["文件管理"])
async def update_file_info(
    file_id: int,
    file_update: schemas.FileUpdate,
    request: Request,
    db: Session = Depends(get_db)
):
    """
    更新文件信息
    
    - **file_id**: 文件 ID
    - **description**: 新描述（可选）
    - **tags**: 新标签（可选）
    - **is_public**: 是否公开（可选）
    """
    db_file = crud.get_file(db, file_id=file_id)
    if not db_file:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="File not found"
        )
    
    updated_file = crud.update_file(db, file=db_file, file_update=file_update)
    
    response = schemas.FileUploadResponse.model_validate(updated_file)
    response_dict = response.model_dump()
    response_dict = add_file_urls(request, updated_file, response_dict)
    
    return response_dict


@app.delete("/files/{file_id}", response_model=schemas.MessageResponse, tags=["文件管理"])
async def delete_file_endpoint(
    file_id: int,
    db: Session = Depends(get_db)
):
    """
    删除文件
    
    - **file_id**: 文件 ID
    
    同时删除文件记录和物理文件
    """
    db_file = crud.get_file(db, file_id=file_id)
    if not db_file:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="File not found"
        )
    
    # 删除物理文件
    file_path = Path(db_file.file_path)
    if file_path.exists():
        delete_file(file_path)
    
    # 删除缩略图
    for thumbnail in db_file.thumbnails:
        thumb_path = Path(thumbnail.file_path)
        if thumb_path.exists():
            delete_file(thumb_path)
    
    # 删除数据库记录
    crud.delete_file(db, file_id=file_id)
    
    return {"message": f"File {file_id} deleted successfully"}


# ========== 图片处理端点 ==========

@app.post("/images/{file_id}/thumbnail", response_model=schemas.MessageResponse, tags=["图片处理"])
async def create_thumbnail_endpoint(
    file_id: int,
    sizes: schemas.ThumbnailCreateRequest,
    db: Session = Depends(get_db)
):
    """
    为图片创建缩略图
    
    - **file_id**: 文件 ID
    - **sizes**: 要创建的尺寸列表
    """
    db_file = crud.get_file(db, file_id=file_id)
    if not db_file or db_file.file_type != "image":
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Image file not found"
        )
    
    file_path = Path(db_file.file_path)
    if not file_path.exists():
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="File not found on disk"
        )
    
    # 创建缩略图
    from .config import THUMBNAIL_SIZES
    for size_name in sizes.sizes:
        if size_name not in THUMBNAIL_SIZES:
            continue
        
        size_tuple = THUMBNAIL_SIZES[size_name]
        from .config import get_thumbnail_path
        output_path = get_thumbnail_path(file_path.name, size_name)
        
        thumbnail_path = create_thumbnail(file_path, size=size_tuple, output_path=output_path)
        
        # 保存缩略图记录
        thumb_info = get_image_info(thumbnail_path)
        crud.create_thumbnail(
            db=db,
            file_id=file_id,
            size_name=size_name,
            filename=thumbnail_path.name,
            file_path=str(thumbnail_path),
            width=thumb_info["width"],
            height=thumb_info["height"],
            file_size=thumb_info["size"]
        )
    
    return {"message": "Thumbnails created successfully"}


@app.post("/images/{file_id}/watermark", response_model=schemas.MessageResponse, tags=["图片处理"])
async def add_watermark(
    file_id: int,
    watermark: schemas.ImageWatermarkRequest,
    db: Session = Depends(get_db)
):
    """
    为图片添加水印
    
    - **file_id**: 文件 ID
    - **text**: 水印文字
    - **position**: 位置
    - **opacity**: 不透明度
    - **font_size**: 字体大小
    """
    db_file = crud.get_file(db, file_id=file_id)
    if not db_file or db_file.file_type != "image":
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Image file not found"
        )
    
    file_path = Path(db_file.file_path)
    if not file_path.exists():
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="File not found on disk"
        )
    
    # 添加水印（覆盖原文件）
    add_text_watermark(
        file_path,
        text=watermark.text,
        position=watermark.position,
        opacity=watermark.opacity,
        font_size=watermark.font_size,
        output_path=file_path
    )
    
    return {"message": "Watermark added successfully"}


# ========== 统计端点 ==========

@app.get("/stats", response_model=schemas.FileStatsResponse, tags=["统计"])
async def get_stats(db: Session = Depends(get_db)):
    """
    获取文件统计信息
    
    返回文件总数、总大小、按类型和状态统计
    """
    stats = crud.get_file_stats(db)
    stats["total_size_formatted"] = format_file_size(stats["total_size"])
    
    return stats


# ========== 根路径 ==========

@app.get("/", tags=["根路径"])
def root():
    """API 根路径"""
    return {
        "message": "文件上传系统 API",
        "version": "1.0.0",
        "docs": "/docs",
        "endpoints": {
            "upload_image": "POST /upload/image",
            "upload_document": "POST /upload/document",
            "upload_multiple": "POST /upload/multiple",
            "list_files": "GET /files",
            "download": "GET /files/download/{file_id}",
            "stats": "GET /stats"
        }
    }


# ========== 主程序入口 ==========

if __name__ == "__main__":
    import uvicorn
    
    print("=" * 50)
    print("🚀 启动文件上传系统")
    print("=" * 50)
    print("📖 文档：http://127.0.0.1:8030/docs")
    print("=" * 50)
    
    uvicorn.run(
        "main:app" if "." not in __name__ else "stage2_advanced.chapter03_file_upload.main:app",
        host="0.0.0.0",
        port=8030,
        reload=True
    )

